package com.scs.application.core.pdf.service;

import com.scs.application.core.pdf.consts.PdfExportConsts;
import com.scs.application.core.pdf.entity.PdfExportFormItem;
import com.scs.application.core.pdf.entity.PdfExportGridColumn;
import com.scs.application.core.pdf.entity.PdfExportParams;
import com.scs.application.core.pdf.enums.PdfPageDirection;
import com.scs.application.core.pdf.enums.SpacingMode;
import com.scs.application.core.pdf.enums.TextShowType;
import com.scs.application.core.pdf.exception.PdfExportException;
import com.scs.application.core.pdf.handler.HeaderHandler;
import com.scs.application.core.pdf.handler.WatermarkingEventHandler;
import com.scs.application.core.utils.DateUtils;
import com.scs.application.core.utils.StringUtils;
import com.google.common.collect.Lists;
import com.itextpdf.barcodes.Barcode128;
import com.itextpdf.barcodes.BarcodeQRCode;
import com.itextpdf.io.font.PdfEncodings;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.xobject.PdfFormXObject;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.borders.Border;
import com.itextpdf.layout.element.*;
import com.itextpdf.layout.property.HorizontalAlignment;
import com.itextpdf.layout.property.TextAlignment;
import com.itextpdf.layout.property.UnitValue;
import com.itextpdf.layout.property.VerticalAlignment;
import com.itextpdf.styledxmlparser.jsoup.helper.StringUtil;
import lombok.Data;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import static com.itextpdf.layout.property.AreaBreakType.NEXT_PAGE;

/**
 * @description:
 */
@Data
public class PdfExportService extends ExportCommonService {
    private static final Paragraph BLANK_ROW = new Paragraph("\n");

    static {
        PdfFontFactory.registerSystemDirectories();
    }

    private OutputStream outputStream;

    private List<PdfExportParams> exportParamsList;


    private PdfFont pdfFont;

    private PdfDocument pdfDoc;

    private Document doc;

    private static final int DEFAULT_FONT_SIZE = 12;

    private static final String DEFAULT_FONT_PROGRAM = "STSong-Light";

    public PdfExportService(OutputStream outputStream, PdfExportParams exportParams) {
        this.outputStream = outputStream;
        this.exportParamsList = Lists.newArrayList(exportParams);
    }

    /**
     * 适用于一次导出不同类型的数据。
     *
     * @param outputStream
     * @param exportParamsList
     */
    public PdfExportService(OutputStream outputStream, List<PdfExportParams> exportParamsList) {
        this.outputStream = outputStream;
        this.exportParamsList = exportParamsList;
    }

    public void export() {
        if (exportParamsList == null) {
            return;
        }
        try {
            this.pdfDoc = new PdfDocument(new PdfWriter(outputStream));
            this.pdfFont = createFont();
            this.doc = new Document(pdfDoc);
            doc.setFont(pdfFont);

            for (PdfExportParams exportParams : exportParamsList) {
                exportSingle(exportParams);
            }
        } catch (Exception e) {
            throw new PdfExportException("PDF导出失败", e);
        } finally {
            if (doc != null) {
                doc.close();
            }
            doc = null;
            pdfDoc = null;
            pdfFont = null;
        }
    }

    private void exportSingle(PdfExportParams exportParams) throws Exception {

        PageSize pageSize = exportParams.getPageSize();
        // 方向
        PageSize tmpPageSize = pageSize;
        if (exportParams.getPageDirection() == PdfPageDirection.TRANSVERSE) {
            tmpPageSize = pageSize.rotate();
        }
        pdfDoc.setDefaultPageSize(tmpPageSize);

        int perRowsOfGrid = exportParams.getPerRowsOfGrid();
        List rawData = exportParams.getGridData() == null ? new ArrayList() : exportParams.getGridData();
        int size = rawData.size();
        int totalPage = (int) Math.ceil(size * 1.0 / perRowsOfGrid);

        PdfFormXObject template = new PdfFormXObject(new Rectangle(pageSize.getHeight() - 50, pageSize.getWidth() - 20, 30, 30));
        pdfDoc.addEventHandler(PdfDocumentEvent.END_PAGE, new HeaderHandler(template, pdfFont, getTotalPage()));

        String watermark = exportParams.getWatermark();
        if (!StringUtil.isBlank(watermark)) {
            pdfDoc.addEventHandler(PdfDocumentEvent.END_PAGE,
                    new WatermarkingEventHandler(pdfFont, watermark, exportParams.getWatermarkFontSize(), exportParams.getBackgroundImageBytes()));
        }

        SpacingMode spacingMode = exportParams.getSpacingMode();

        for (int i = 0; i < totalPage; i++) {
            PdfPage pdfPage = doc.getPdfDocument().addNewPage();
            int pageNumber = pdfPage.getDocument().getPageNumber(pdfPage);
            if (pageNumber != 1) {
                doc.add(new AreaBreak(NEXT_PAGE));
            }
            addTitle(exportParams, doc);
            if (spacingMode == SpacingMode.NORMAL) {
                doc.add(BLANK_ROW);
            }

            addHeader(exportParams, doc);
            if (spacingMode == SpacingMode.NORMAL) {
                doc.add(BLANK_ROW);
            }

            int beginIndex = i * perRowsOfGrid;
            int endIndex = Math.min(size, (i + 1) * perRowsOfGrid);
            List<?> data = rawData.subList(beginIndex, endIndex);
            addGrid(exportParams, doc, data);
            if (spacingMode == SpacingMode.NORMAL) {
                doc.add(BLANK_ROW);
            }
            addFooter(exportParams, doc);
        }
    }


    private void addGrid(PdfExportParams exportParams, Document doc, List data) throws Exception {

        List<PdfExportGridColumn> columns = exportParams.getGridColumns();
        if (columns == null) {
            return;
        }
        int gridSpan = exportParams.getGridSpan();

        Table gridTable = new Table(UnitValue.createPercentArray(gridSpan)).useAllAvailableWidth();
        gridTable.setBorder(Border.NO_BORDER);
        gridTable.setKeepTogether(true);
        for (PdfExportGridColumn column : columns) {
            Cell cell = new Cell(1, column.getSpan());
            cell.add(new Paragraph(String.format("%s", column.getLabel())).setFontSize(PdfExportConsts.GRID_HEADER_FONT_SIZE))
                    .setTextAlignment(TextAlignment.CENTER).setBold();
            gridTable.addHeaderCell(cell);
        }

        for (Object datum : data) {
            for (PdfExportGridColumn column : columns) {
                String fieldName = column.getField();
                Object value = null;

                try {
                    if (fieldName.equalsIgnoreCase("rowIndex")) {
                        //如果是序号
                        value = gridTable.getNumberOfRows() + 1;
                    } else {
                        value = getFieldValue(datum, fieldName);
                    }
                } catch (NoSuchFieldException e) {
                    throw e;
                }

                String formatValue = formatValue(value, column.getField());

                Cell cell = new Cell(1, column.getSpan());

                PageSize defaultPageSize = doc.getPdfDocument().getDefaultPageSize();

                float columnWidth = defaultPageSize.getWidth() / gridSpan * column.getSpan();


                TextShowType textShowType = column.getTextShowType();
                int height = column.getHeight();

                TextAlignment textAlignment = column.getTextAlignment();

                int dataFontSize = PdfExportConsts.GRID_DATA_FONT_SIZE;
                if (textShowType == TextShowType.PLAIN) {
                    // 每行显示字符个数
                    int numberOfRow = (int) Math.floor(columnWidth / (dataFontSize / 2));
                    if (StringUtils.isContainChinese(formatValue)) {
                        // 包含中文每行显示的字符个数较少
                        numberOfRow = (int) Math.floor(columnWidth / (dataFontSize + 3));
                    }

                    int rows = (int) Math.ceil(formatValue.length() * 1.0 / numberOfRow);
                    Paragraph paragraph = new Paragraph();
                    paragraph.add(new Text(formatValue));
                    paragraph.setFontSize(dataFontSize);
                    cell.add(paragraph).setTextAlignment(textAlignment != null ? textAlignment : TextAlignment.CENTER);
                } else if (textShowType == TextShowType.BARCODE) {
                    Barcode128 code128 = new Barcode128(doc.getPdfDocument());
                    code128.setFont(column.isShowBarcodeText() ? pdfFont : null);
                    code128.setCode(formatValue);

                    PdfFormXObject xObject = code128.createFormXObject(doc.getPdfDocument());
                    cell.add(new Image(xObject).setAutoScale(true).scale(0.7f, 0.7f));

                } else if (textShowType == TextShowType.QRCODE) {
                    BarcodeQRCode barcodeQRCode = new BarcodeQRCode(formatValue);

                    PdfFormXObject xObject = barcodeQRCode.createFormXObject(doc.getPdfDocument());
                    cell.add(new Image(xObject).setHeight(height).setAutoScale(true));
                }

                gridTable.addCell(cell);
            }
        }

        long count = columns.stream().filter(o -> o.isSummary()).count();
        if (count == 0) {
            doc.add(gridTable);
            return;
        }

        for (PdfExportGridColumn column : columns) {
            Cell cell = new Cell(1, column.getSpan());
            if (!column.isSummary()) {
                gridTable.addFooterCell(cell);
                continue;
            }
            double sum = data.stream().mapToDouble(o -> {
                try {
                    Object value = getFieldValue(o, column.getField());
                    if (value == null || !StringUtils.isNumeric(value.toString())) {
                        return 0.0;
                    }
                    return Double.parseDouble(value.toString());
                } catch (Exception e) {
                    return 0.0;
                }
            }).sum();
            cell.add(new Paragraph(String.format("%.2f", sum))).setTextAlignment(TextAlignment.CENTER);
            gridTable.addFooterCell(cell);
        }

        doc.add(gridTable);
    }

    private Object getFieldValue(Object ins, String fieldName) throws NoSuchFieldException, IllegalAccessException {
        try {
            Object value = null;
            if (ins instanceof Map) {
                value = ((Map) ins).get(fieldName);
            } else {
                Field field = ins.getClass().getDeclaredField(fieldName);
                field.setAccessible(true);
                value = field.get(ins);
            }
            return value;
        } catch (NoSuchFieldException e) {
            throw e;
        } catch (IllegalAccessException e) {
            throw e;
        }
    }


    private void addFooter(PdfExportParams exportParams, Document doc) {
        _addHeaderOrFooter(exportParams, doc, exportParams.getFooters());
    }

    private void addHeader(PdfExportParams exportParams, Document doc) {
        _addHeaderOrFooter(exportParams, doc, exportParams.getHeaders());
    }

    private void _addHeaderOrFooter(PdfExportParams exportParams, Document doc, List<PdfExportFormItem> items) {

        if (items == null || items.isEmpty()) {
            return;
        }

        Table table = new Table(UnitValue.createPercentArray(24)).useAllAvailableWidth();
        table.setBorder(Border.NO_BORDER);

        // 获取标签最大长度
        int maxLength = items.stream().mapToInt(o -> o.getLabel().length()).max().getAsInt();

        for (PdfExportFormItem item : items) {
            int offset = item.getOffset();
            if (offset > 0) {
                Cell cell = new Cell(1, offset).setBorder(Border.NO_BORDER);
                table.addCell(cell);
            }

            Object value = item.getValue();

            Cell cell = new Cell(1, item.getSpan());
            cell.setBorder(Border.NO_BORDER);

            String label = item.getLabel();
            String formatValue = formatValue(value, item.getField());
            TextShowType textShowType = item.getTextShowType();
            float height = item.getHeight();

            if (textShowType == TextShowType.PLAIN) {
                cell.add(new Paragraph(
                        new Text(StringUtils.repeat("\t", (maxLength - label.length())) + label).setBold()).setFontSize(PdfExportConsts.HEADER_FONT_SIZE)
                        .add(String.format("%s%s", "\t", formatValue)).setFontSize(PdfExportConsts.HEADER_FONT_SIZE)
                );
            } else if (textShowType == TextShowType.BARCODE) {

                Barcode128 code128 = new Barcode128(doc.getPdfDocument());
                code128.setFont(item.isShowBarcodeText() ? pdfFont : null);
                code128.setCode(formatValue);
                code128.setBarHeight(height);

                PdfFormXObject xObject = code128.createFormXObject(doc.getPdfDocument());
                cell.add(new Paragraph(new Text(label).setBold()).add("\t ")
                        .add(new Image(xObject).setHeight(height).setHorizontalAlignment(HorizontalAlignment.CENTER)));

            } else if (textShowType == TextShowType.QRCODE) {
                BarcodeQRCode barcodeQRCode = new BarcodeQRCode(formatValue);

                PdfFormXObject xObject = barcodeQRCode.createFormXObject(doc.getPdfDocument());
                cell.add(new Paragraph(new Text(label).setBold()).add("\t ")
                        .add(new Image(xObject).setHeight(height).setRelativePosition(0, height / 2, 0, 0)));
            }
            table.addCell(cell);
        }
        doc.add(table);

    }

    private String formatValue(Object value, String field) {
        if (value == null) {
            return "";
        }
        if (value instanceof Date) {
            return DateUtils.format((Date) value, "yyyy-MM-dd");
        }
        return value.toString();
    }

    private void addTitle(PdfExportParams exportParams, Document doc) {
        String subTitle = exportParams.getSubTitle();

        Table table = new Table(UnitValue.createPercentArray(24)).useAllAvailableWidth();

        int totalSpan = 24;
        int titleSpan = totalSpan;
        int rowspan = 3;

        String project = exportParams.getProject();
        String qrcodeText = exportParams.getQrcodeText();
        String barcodeText = exportParams.getBarcodeText();
        if (StringUtils.isNotBlank(qrcodeText) || StringUtils.isNotBlank(barcodeText)) {
            titleSpan -= 12;
        }

        Cell projectCell = new Cell(1, titleSpan);
        projectCell.setBorder(Border.NO_BORDER);
        projectCell.add(new Paragraph(StringUtils.defaultString(project))
                .setTextAlignment(TextAlignment.LEFT).setFontSize(PdfExportConsts.PROJECT_FONT_SIZE).setUnderline()
        );
        table.addHeaderCell(projectCell);

        if (StringUtils.isNotBlank(qrcodeText)) {
            Cell qrcodeCell = new Cell(rowspan, StringUtils.isNotBlank(barcodeText) ? 4 : 12);
            BarcodeQRCode barcodeQRCode = new BarcodeQRCode(qrcodeText);

            PdfFormXObject xObject = barcodeQRCode.createFormXObject(doc.getPdfDocument());
            qrcodeCell.add(new Image(xObject).scaleToFit(90, 90));
            table.addHeaderCell(qrcodeCell.setVerticalAlignment(VerticalAlignment.MIDDLE).setBorder(Border.NO_BORDER));
        }

        if (StringUtils.isNotBlank(barcodeText)) {
            Cell barcodeCell = new Cell(rowspan, StringUtils.isNotBlank(qrcodeText) ? 8 : 12);
            Barcode128 code128 = new Barcode128(doc.getPdfDocument());
            code128.setCode(barcodeText);
            code128.setBaseline(-1);

            PdfFormXObject xObject = code128.createFormXObject(doc.getPdfDocument());
            barcodeCell.add(new Image(xObject).setAutoScale(true));
            table.addHeaderCell(barcodeCell.setVerticalAlignment(VerticalAlignment.MIDDLE).setBorder(Border.NO_BORDER));
        }


        Cell titleCell = new Cell(1, titleSpan);
        titleCell.setBorder(Border.NO_BORDER);
        titleCell.add(new Paragraph(exportParams.getTitle())
                .setTextAlignment(TextAlignment.CENTER).setFontSize(PdfExportConsts.TITLE_FONT_SIZE).setBold());
        table.addHeaderCell(titleCell);


        if (StringUtils.isNotBlank(subTitle)) {
            Cell subTitleCell = new Cell(1, titleSpan);
            subTitleCell.setBorder(Border.NO_BORDER);
            subTitleCell.add(new Paragraph(subTitle)
                    .setTextAlignment(TextAlignment.CENTER).setFontSize(PdfExportConsts.SUBTITLE_FONT_SIZE));
            table.addHeaderCell(subTitleCell);
        }
        doc.add(table);
    }

    protected PdfFont createFont() throws IOException {
        return PdfFontFactory.createRegisteredFont("simsun", PdfEncodings.IDENTITY_H, true);
//        return PdfFontFactory.createFont("微软雅黑-light", PdfEncodings.IDENTITY_H,true);

        // return PdfFontFactory.createFont(DEFAULT_FONT_PROGRAM, "UniGB-UCS2-H", true);
    }

    /**
     * 获取总页数
     *
     * @return
     */
    private int getTotalPage() {
        if (exportParamsList == null || exportParamsList.isEmpty()) {
            return 0;
        }
        int totalPage = 0;
        for (PdfExportParams params : exportParamsList) {
            int perRowsOfGrid = params.getPerRowsOfGrid();
            List rawData = params.getGridData() == null ? new ArrayList() : params.getGridData();
            int size = rawData.size();
            totalPage += (int) Math.ceil(size * 1.0 / perRowsOfGrid);
        }
        return totalPage;
    }
}
